package com.nc.mem.serial.graph.cust;

import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;

import com.nc.mem.serial.Serializer;
import com.nc.mem.serial.graph.Context;
import com.nc.mem.serial.graph.GraphSerializer;
import com.nc.mem.serial.graph.Representation;
import com.nc.mem.serial.graph.asm.GraphSerializerFactory;
import com.nc.mem.serial.graph.boot.RegistrationListener;
import com.nc.mem.serial.graph.common.CollectionSerializer;
import com.nc.mem.serial.graph.container.CollectionRegistry;
import com.nc.mem.serial.graph.container.SerializerRegistry;


/**
 * Format is [colId+tag][colSize]([colCompTypeId][0|1][data])? where [data] can have two formats: <br>
 * <br>
 * Format 1: Ids of the elements will be written as well as the payload <br>
 * <br>
 * [data] = ([obj1Id + tag][objNPayload])(....)([objNId + tag][objNPayload]) <br>
 * <br>
 * Format 2: Only of the payloads will be written<br>
 * <br>
 * [data] = ([objNPayload])(....)([objNPayload])
 * 
 * @author cmuramoto
 */

public final class FixedTypeCollectionSerializer extends GraphSerializer implements RegistrationListener {

	public static final FixedTypeCollectionSerializer INSTANCE = new FixedTypeCollectionSerializer();

	private FixedTypeCollectionSerializer() {
	}

	@SuppressWarnings("unchecked")
	@Override
	public void doRead(final ByteBuffer src, final Object obj) {

		final Collection<Object> ret = (Collection<Object>) obj;
		final int sz = (int) (Representation.readLong(src) >> 1);

		if (sz > 0) {
			final Class<?> clazz = Representation.idToClass(src);
			final Serializer ser = GraphSerializerFactory.serializer(clazz);

			if (ser instanceof GraphSerializer) {
				final boolean tagObjects = Representation.readBoolean(src);
				final GraphSerializer gs = (GraphSerializer) ser;
				Object o;
				int s;
				final Context context = Context.current().start();
				try {
					if (tagObjects) {
						for (int i = 0; i < sz; i++) {
							s = Representation.readInt(src);
							if ((s & 1 << 1) != 0) {
								ret.add(context.fromId(s));
							} else {
								o = gs.instantiate(src);
								context.registerId(o, s);
								gs.doRead(src, o);
								ret.add(o);
							}
						}
					} else {
						for (int i = 0; i < sz; i++) {
							o = gs.instantiate(src);
							gs.doRead(src, o);
							ret.add(o);
						}
					}
				} finally {
					context.clear();
				}
			}
		}
	}

	@Override
	public void doWrite(final ByteBuffer dest, final Object obj) {
		@SuppressWarnings("unchecked")
		final CollectionHolder<Object> col = (CollectionHolder<Object>) obj;
		final int sz = col.size();

		Representation.writeClassId(dest, col.collectionClass());
		col.writeKindAndSize(dest);

		if (sz > 0) {
			final Iterator<Object> itr = col.iterator();
			Object curr = itr.next();
			final Class<? extends Object> clazz = curr.getClass();
			Representation.writeClassId(dest, clazz);
			final Serializer ser = GraphSerializerFactory.serializer(clazz);

			if (ser instanceof GraphSerializer) {
				final boolean tagObjects = !col.isOnlyPayload();
				Representation.writeBoolean(dest, tagObjects);
				final GraphSerializer gs = (GraphSerializer) ser;
				final Context context = Context.current().start();

				try {
					if (tagObjects) {
						int s;
						do {
							s = context.registerObject(curr);
							Representation.writeInt(dest, s);
							if ((s & 1 << 1) == 0) {
								gs.doWrite(dest, curr);
							}
						} while (itr.hasNext() && (curr = itr.next()) != null);
					} else {
						do {
							gs.doWrite(dest, curr);
						} while (itr.hasNext() && (curr = itr.next()) != null);
					}
				} finally {
					context.clear();
				}
			}
		}
	}

	@Override
	public Object instantiate(final ByteBuffer src) {
		final int id = Representation.readInt(src);
		final int p = src.position();
		final long szAndKind = Representation.readLong(src);

		Object ret;

		if ((szAndKind & 1) != 0) {
			final int s = Representation.readInt(src);
			final Context ctx = Context.current();

			Comparator<?> c = (Comparator<?>) ctx.fromId(s);

			if (c == null) {
				c = (Comparator<?>) GraphSerializerFactory.serializer(Representation.idToClass(src)).read(src, s);
			}

			ret = CollectionRegistry.INSTANCE.newCollection(id, c);
		} else {
			src.position(p);
			ret = CollectionRegistry.INSTANCE.newCollection(id, (int) (szAndKind >> 1));
		}

		return ret;
	}

	@Override
	public void onClassRegistered(final Class<?> clazz, final int id) {
		SerializerRegistry repo;
		if (Collection.class.isAssignableFrom(clazz) && !(repo = SerializerRegistry.getInstance()).hasAlternative(clazz)) {

			if (!Modifier.isAbstract(clazz.getModifiers())) {
				repo.register(clazz, CollectionSerializer.INSTANCE);
			}

			Class<?> c = clazz;
			while (c != null && c != Object.class && Collection.class.isAssignableFrom(c) && repo.registerAlternative(c, INSTANCE)) {
				c = c.getSuperclass();
			}
			c = clazz;
			while (c != null && c != Object.class) {
				final Class<?>[] interfaces = c.getInterfaces();

				for (final Class<?> intf : interfaces) {
					if (Collection.class.isAssignableFrom(intf)) {
						repo.registerAlternative(intf, INSTANCE);
					}
				}
				c = c.getSuperclass();
			}
		}
	}
}